﻿//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//    Copyright (c) 2017 Daniel Rau
//
//    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
//    (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 
//    merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
//    furnished to do so, subject to the following conditions:
//    
//    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
//    BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Runtime.CompilerServices;
using System.Text;

namespace Base85P5
{
    /// <summary>
    /// Base85 Encoding algorithm with possibility of compression.
    /// Encodes each 4 bytes block of data to a 5 bytes block (maximum) encoded data.
    /// </summary>
    public static class Base85P5
    {
        #region Fields
        /// <summary>
        /// Alphabet used to encode the bytes.
        /// Replaces the given index with a printable char.
        /// Used for encoding.
        /// </summary>
        public static readonly char[] _alphabet =
        {
            'a', 'b', 'c', 'd', 'e', 'f','g', 'h', 'i', 'j',  //0 - 9
            'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r','s', 't',  //10-19
            'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', //20-29
            'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', //30-39
            'O', 'P','Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',  //40-49
            'Y', 'Z', '0', '1', '2', '3', '4', '5','6', '7',  //50-59
            '8', '9', ' ', '#', '$', '%', '\'', '(', ')', '*',//60-69
            '+', ',', '-', '.', '/', ':', ';', '=', '?', '@', //70-79
            '[', ']', '^', '_', '`', '{', '|', '}', '!', '~', //80-89

            //85 = compression char; means all bytes have value of 32
            //86 = compression char; means all bytes have value of 48
            //87 = compression char; means same block sum as previous block sum
            //88 = prefix/suffix + padding char
            //89 = prefix/suffix
        };

        /// <summary>
        /// Reverse direction of alphabet. Replaces a printable base 85 char with the index it has before.
        /// Used for decoding.
        /// </summary>
        /// <remarks>
        /// Index = ASCII value of char minus <see cref="_offsetAlphabetMinChar"/>.
        /// 
        /// Also there are some gaps here because we don't use continuous ASCII chars.
        /// </remarks>
        private static readonly uint[] _indexToAlphabet =
        {
            62, 88, 0/*gap*/, 63, 64, 65, 0/*gap*/, 66, 67, 68, //0 - 9
            69, 70, 71, 72, 73, 74, 52, 53, 54, 55,             //10-19
            56, 57, 58, 59, 60, 61, 75, 76, 0/*gap*/, 77,       //20-29
            0/*gap*/, 78, 79, 26, 27, 28, 29, 30, 31, 32,       //30-39
            33, 34, 35, 36, 37, 38, 39, 40, 41, 42,             //40-49
            43, 44, 45, 46, 47, 48, 49, 50, 51, 80,             //50-59
            0/*gap*/, 81, 82, 83, 84, 0, 1, 2, 3, 4,            //60-69
            5, 6, 7, 8, 9, 10, 11, 12, 13, 14,                  //70-79
            15, 16, 17, 18, 19, 20, 21, 22, 23, 24,             //80-89
            25, 85, 86, 87, 89                                  //90-94
        };


        /// <summary>
        /// Compression char used to encode a 4 byte block where all bytes have a value of 32.
        /// This is a constant because it is used in a switch/case.
        /// </summary>
        private const char _compressCharAll32 = '{'; //_alphabet[85];

        /// <summary>
        /// Compression char used to encode a 4 byte block where all bytes have a value of 48.
        /// This is a constant because it is used in a switch/case.
        /// </summary>
        private const char _compressCharAll48 = '|'; //_alphabet[86];

        /// <summary>
        /// Compression char used to encode a 4 byte block with the same value the previous 4 byte block has.
        /// This is a constant because it is used in a switch/case.
        /// </summary>
        private const char _compressCharSameSum = '}'; //_alphabet[87];


        /// <summary>
        /// Char used for padding as "empty" byte in original byte array (where length isn't a multiple of 4).
        /// </summary>
        private static readonly char _paddingChar = _alphabet[88];

        /// <summary>
        /// Plain index of the padding char but without substracting <see cref="_offsetAlphabetMinChar"/>.
        /// </summary>
        private static readonly uint _paddingCharIndexNoOffset = 33;


        //characters used to form the prefix for compression and no compression
        private static readonly char _prefixChar1 = _alphabet[88];
        private static readonly char _prefixChar2 = _alphabet[89];
        private static readonly char _prefixChar3Compress = _compressCharSameSum;

        /// <summary>
        /// Prefix as string literal "!~" used for the encoded data with no compression.
        /// </summary>
        private static readonly string _prefixNoCompress = string.Concat(_prefixChar1, _prefixChar2);

        /// <summary>
        /// Prefix as string literal "!~}" used for the encoded data with compression but no padding.
        /// </summary>
        private static readonly string _prefixCompress = string.Concat(_prefixChar1, _prefixChar2, _prefixChar3Compress);

        /// <summary>
        /// Prefix as string literal "!~!" used for the encoded data with compression and padding.
        /// </summary>
        private static readonly string _prefixCompressPadding = string.Concat(_prefixChar1, _prefixChar2, _prefixChar1);


        //characters used to form the suffix
        private static readonly char _suffixChar1 = _alphabet[89];
        private static readonly char _suffixChar2 = _alphabet[88];

        /// <summary>
        /// Suffix as string literal "~!" used for the encoded data (regardless if compressed or not).
        /// </summary>
        private static readonly string _suffix = string.Concat(_suffixChar1, _suffixChar2);


        //exponential results of base 85 as constants to save CPU instructions
        private const uint _pow85_1 = 1;
        private const uint _pow85_2 = 85;
        private const uint _pow85_3 = 85 * 85;
        private const uint _pow85_4 = 85 * 85 * 85;
        private const uint _pow85_5 = 85 * 85 * 85 * 85;


        /// <summary>
        /// Constant sum of a 4 byte block where all bytes have a value of 32.
        /// </summary>
        private const uint _sumAllBytes32 = 538976288;

        /// <summary>
        /// Constant sum of a 4 byte block where all bytes have a value of 48.
        /// </summary>
        private const uint _sumAllBytes48 = 808464432;


        /// <summary>
        /// Minimum value of the lowest ASCII char used by this algorithm.
        /// Used as offset for index calculations of the alphabet.
        /// </summary>
        private const uint _offsetAlphabetMinChar = 32;

        private static readonly ServiceStack.Text.RecyclableMemoryStreamManager _memManager = new ServiceStack.Text.RecyclableMemoryStreamManager();
        #endregion


        #region Methods

        #region Public Methods
        /// <summary>
        /// Decodes a base85 encoded string, regardless if compressed or not.
        /// </summary>
        /// <param name="s">Base85 encoded string to decode.</param>
        /// <returns>Decoded data.</returns>
        /// <exception cref="InvalidOperationException">Thrown if given string is not base85 encoded.</exception>
        public unsafe static byte[] Decode(string s)
        {
            if (s.EndsWith(_suffix))
            {
                if (s.StartsWith(_prefixCompressPadding))
                {
                    return DecodeCompressPadding(s);
                }
                else if (s.StartsWith(_prefixCompress))
                {
                    return DecodeCompressNoPadding(s);
                }
                else if (s.StartsWith(_prefixNoCompress))
                {
                    return DecodeNoCompress(s);
                }
            }

            throw new InvalidOperationException("String is not Base85P5 encoded.");
        }

        /// <summary>
        /// Decodes a base85 encoded string, regardless if compressed or not.
        /// </summary>
        /// <param name="s">Base85 encoded string to decode.</param>
        /// <param name="bytes">Decoded data or null.</param>
        /// <returns>True if string was base85 encoded, false if string was not base85 encoded and could not be decoded.</returns>
        public unsafe static bool TryDecode(string s, out byte[] bytes)
        {
            bytes = null;

            if (s.EndsWith(_suffix))
            {
                if (s.StartsWith(_prefixCompressPadding))
                {
                    bytes = DecodeCompressPadding(s);
                    return true;
                }
                else if (s.StartsWith(_prefixCompress))
                {
                    bytes = DecodeCompressNoPadding(s);
                    return true;
                }
                else if (s.StartsWith(_prefixNoCompress))
                {
                    bytes = DecodeNoCompress(s);
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Encodes an array of bytes to a base85 string.
        /// </summary>
        /// <param name="bytes">Byte array to encode.</param>
        /// <returns>Base85 encoded string.</returns>
        public unsafe static string Encode(byte[] bytes)
        {
            int countFractionalBytes = bytes.Length % 4;
            int countPaddingBytes = (countFractionalBytes > 0) ? 4 : 0;
            int count = 2; //omit prefix
            char[] block = new char[5];

            //+4 = prefix & suffix
            int supposedLength = (bytes.Length - countFractionalBytes + countPaddingBytes) +
                (int)((bytes.Length - countFractionalBytes + countPaddingBytes) * 0.25f + 4);
            string result = new string('\0', supposedLength);

            fixed (byte* pSource = bytes)
            {
                fixed (char* pTarget = result)
                {
                    fixed (char* pBlock = block)
                    {
                        fixed (char* pAlphabet = _alphabet)
                        {
                            //prefix "!~"
                            AddPrefixNoCompress(pTarget);

                            #region Actual data (all complete 4 byte blocks)
                            for (int i = 0; i < bytes.Length - countFractionalBytes; i += 4)
                            {
                                uint sum = (uint)((pSource[i] << 24) | (pSource[i + 1] << 16) | (pSource[i + 2] << 8) | pSource[i + 3]);

                                pBlock[0] = pAlphabet[sum - sum / 85 * 85];
                                sum /= 85;
                                pBlock[1] = pAlphabet[sum - sum / 85 * 85];
                                sum /= 85;
                                pBlock[2] = pAlphabet[sum - sum / 85 * 85];
                                sum /= 85;
                                pBlock[3] = pAlphabet[sum - sum / 85 * 85];
                                pBlock[4] = pAlphabet[sum / 85];

                                for (int j = 0; j < 5; j++)
                                {
                                    pTarget[count++] = pBlock[j];
                                }
                            }
                            #endregion

                            #region Padding (we have a fractional 4 byte block)
                            if (countFractionalBytes != 0)
                            {
                                uint sum = 0;
                                int offset = bytes.Length - countFractionalBytes;

                                //make sum as normal for all bytes left
                                for (int i = 0; i < countFractionalBytes; i++)
                                {
                                    sum |= (uint)(pSource[offset + i] << (24 - (i * 8)));
                                }

                                //fill remaining bytes with padding char
                                for (int i = countFractionalBytes; i < 4; i++)
                                {
                                    sum |= (uint)(_paddingChar << (24 - (i * 8)));
                                }

                                pBlock[0] = pAlphabet[sum - sum / 85 * 85];
                                sum /= 85;
                                pBlock[1] = pAlphabet[sum - sum / 85 * 85];
                                sum /= 85;
                                pBlock[2] = pAlphabet[sum - sum / 85 * 85];
                                sum /= 85;
                                pBlock[3] = pAlphabet[sum - sum / 85 * 85];
                                pBlock[4] = pAlphabet[sum / 85];

                                for (int j = 0; j < 5; j++)
                                {
                                    pTarget[count++] = pBlock[j];
                                }
                            }
                            #endregion

                            //suffix "~!"
                            AddSuffix(pTarget, ref count);
                        }
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Encodes an array of bytes to a base85 string and compresses it.
        /// </summary>
        /// <param name="bytes">Byte array to encode.</param>
        /// <returns>Base85 encoded string.</returns>
        public unsafe static string EncodeAndCompress(byte[] bytes)
        {
            int countFractionalBytes = bytes.Length % 4;
            int countPaddingBytes = (countFractionalBytes > 0) ? 4 : 0;
            char[] block = new char[5];
            uint lastSum = 0;

            //+5 = prefix & suffix
            //+5 = length of original byte array (also encoded)
            int supposedLength = (bytes.Length - countFractionalBytes + countPaddingBytes) +
                (int)((bytes.Length - countFractionalBytes + countPaddingBytes) * 0.25f + 5 + 5);
            StringBuilder sb = new StringBuilder(null, supposedLength);

            fixed (byte* pSource = bytes)
            {
                fixed (char* pBlock = block)
                {
                    fixed (char* pAlphabet = _alphabet)
                    {
                        //append prefix depending on padding
                        if (countFractionalBytes != 0)
                        {
                            //with padding: "!~!"
                            sb.Append(_prefixCompressPadding);
                        }
                        else
                        {
                            //without padding: "!~}"
                            sb.Append(_prefixCompress);
                        }

                        #region Original length
                        //add length of original byte array
                        uint sum = (uint)bytes.Length;

                        pBlock[0] = pAlphabet[sum - sum / 85 * 85];
                        sum /= 85;
                        pBlock[1] = pAlphabet[sum - sum / 85 * 85];
                        sum /= 85;
                        pBlock[2] = pAlphabet[sum - sum / 85 * 85];
                        sum /= 85;
                        pBlock[3] = pAlphabet[sum - sum / 85 * 85];
                        pBlock[4] = pAlphabet[sum / 85];

                        for (int j = 0; j < 5; j++)
                        {
                            sb.Append(pBlock[j]);
                        }
                        #endregion

                        #region Actual data (all complete 4 byte blocks)
                        for (int i = 0; i < bytes.Length - countFractionalBytes; i += 4)
                        {
                            sum = (uint)((pSource[i] << 24) | (pSource[i + 1] << 16) | (pSource[i + 2] << 8) | pSource[i + 3]);

                            switch (sum)
                            {
                                case _sumAllBytes32: //all bytes have value of 32
                                    sb.Append(pAlphabet[85]);
                                    break;

                                case _sumAllBytes48: //all bytes have value of 48
                                    sb.Append(pAlphabet[86]);
                                    break;

                                default:
                                    if (lastSum == sum) //previous block has same value as current block
                                    {
                                        sb.Append(pAlphabet[87]);
                                        lastSum = sum;
                                    }
                                    else
                                    {
                                        lastSum = sum;

                                        pBlock[0] = pAlphabet[sum - sum / 85 * 85];
                                        sum /= 85;
                                        pBlock[1] = pAlphabet[sum - sum / 85 * 85];
                                        sum /= 85;
                                        pBlock[2] = pAlphabet[sum - sum / 85 * 85];
                                        sum /= 85;
                                        pBlock[3] = pAlphabet[sum - sum / 85 * 85];
                                        pBlock[4] = pAlphabet[sum / 85];

                                        for (int j = 0; j < 5; j++)
                                        {
                                            sb.Append(pBlock[j]);
                                        }
                                    }

                                    break;
                            }
                        }
                        #endregion

                        #region Padding (we have a fractional 4 byte block)
                        //padding
                        if (countFractionalBytes != 0)
                        {
                            sum = 0;
                            int offset = bytes.Length - countFractionalBytes;

                            //make sum as normal for all bytes left
                            for (int i = 0; i < countFractionalBytes; i++)
                            {
                                sum |= (uint)(pSource[offset + i] << (24 - (i * 8)));
                            }

                            //fill remaining bytes with padding char
                            for (int i = countFractionalBytes; i < 4; i++)
                            {
                                sum |= (uint)(_paddingChar << (24 - (i * 8)));
                            }

                            pBlock[0] = pAlphabet[sum - sum / 85 * 85];
                            sum /= 85;
                            pBlock[1] = pAlphabet[sum - sum / 85 * 85];
                            sum /= 85;
                            pBlock[2] = pAlphabet[sum - sum / 85 * 85];
                            sum /= 85;
                            pBlock[3] = pAlphabet[sum - sum / 85 * 85];
                            pBlock[4] = pAlphabet[sum / 85];

                            for (int j = 0; j < 5; j++)
                            {
                                sb.Append(pBlock[j]);
                            }
                        }
                        #endregion

                        //suffix "~!"
                        sb.Append(_suffix);
                    }
                }
            }

            return sb.ToString();
        }
        #endregion


        #region Private Methods
        /// <summary>
        /// Prepends the prefix for no compressing to the target string.
        /// </summary>
        /// <param name="pTarget">Char pointer to the string.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe static void AddPrefixNoCompress(char* pTarget)
        {
            //prefix "!~"
            pTarget[0] = _prefixChar1;
            pTarget[1] = _prefixChar2;
        }

        /// <summary>
        /// Appends the suffix to the target string and increases the given char counter.
        /// </summary>
        /// <param name="pTarget">Char pointer to the string.</param>
        /// <param name="count">Reference to the current counter/index of the string char.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe static void AddSuffix(char* pTarget, ref int count)
        {
            //suffix "~!"
            pTarget[count++] = _suffixChar1;
            pTarget[count++] = _suffixChar2;
        }

        // <summary>
        /// Decodes an uncompressed base85 string internally.
        /// </summary>
        /// <param name="s">String to decode.</param>
        /// <returns>Decoded byte array.</returns>
        private unsafe static byte[] DecodeNoCompress(string s)
        {
            byte[] decodedBlock = new byte[4];
            int supposedLength = (s.Length - 4) / 5 * 4; //not quite correct, padding may vary, but calculation is only for a big enough allocated block of memory

            fixed (char* pSource = s)
            {
                fixed (uint* pIndexToAlphabet = _indexToAlphabet)
                {
                    fixed (byte* pBlock = decodedBlock)
                    {
                        using (var ms = _memManager.GetStream(String.Empty, supposedLength, false))
                        {
                            uint sum;

                            #region Complete 4 byte blocks
                            //omit prefix and suffix and handle last block seperately because of possible padding
                            for (int i = 2; i < s.Length - 7; i += 5)
                            {
                                sum = pIndexToAlphabet[pSource[i] - _offsetAlphabetMinChar] * _pow85_1 +
                                    pIndexToAlphabet[pSource[i + 1] - _offsetAlphabetMinChar] * _pow85_2 +
                                    pIndexToAlphabet[pSource[i + 2] - _offsetAlphabetMinChar] * _pow85_3 +
                                    pIndexToAlphabet[pSource[i + 3] - _offsetAlphabetMinChar] * _pow85_4 +
                                    pIndexToAlphabet[pSource[i + 4] - _offsetAlphabetMinChar] * _pow85_5;

                                pBlock[3] = (byte)(sum & 0xff);
                                pBlock[2] = (byte)((sum & 0xff00) >> 8);
                                pBlock[1] = (byte)((sum & 0xff0000) >> 16);
                                pBlock[0] = (byte)((sum & 0xff000000) >> 24);

                                ms.Write(decodedBlock, 0, 4);
                            }
                            #endregion

                            #region Last block with possible padding
                            //handle last block which may have padding
                            int offset = s.Length - 7;
                            sum = pIndexToAlphabet[pSource[offset] - _offsetAlphabetMinChar] * _pow85_1 +
                                pIndexToAlphabet[pSource[offset + 1] - _offsetAlphabetMinChar] * _pow85_2 +
                                pIndexToAlphabet[pSource[offset + 2] - _offsetAlphabetMinChar] * _pow85_3 +
                                pIndexToAlphabet[pSource[offset + 3] - _offsetAlphabetMinChar] * _pow85_4 +
                                pIndexToAlphabet[pSource[offset + 4] - _offsetAlphabetMinChar] * _pow85_5;

                            pBlock[3] = (byte)(sum & 0xff);
                            pBlock[2] = (byte)((sum & 0xff00) >> 8);
                            pBlock[1] = (byte)((sum & 0xff0000) >> 16);
                            pBlock[0] = (byte)((sum & 0xff000000) >> 24);

                            int numBytesToWrite = 4;
                            for (int i = 1; i < 4; i++)
                            {
                                if (pBlock[i] == _paddingCharIndexNoOffset)
                                {
                                    numBytesToWrite = i;
                                    break;
                                }
                            }

                            ms.Write(decodedBlock, 0, numBytesToWrite);
                            #endregion

                            return ms.ToArray();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Decodes a compressed base85 string without padding internally.
        /// </summary>
        /// <param name="s">String to decode.</param>
        /// <returns>Decoded byte array.</returns>
        private unsafe static byte[] DecodeCompressNoPadding(string s)
        {
            byte[] decodedBlock = new byte[4];

            fixed (char* pSource = s)
            {
                fixed (uint* pIndexToAlphabet = _indexToAlphabet)
                {
                    fixed (byte* pBlock = decodedBlock)
                    {
                        #region Length of original byte array before encoding
                        int totalLength = (int)(
                                            pIndexToAlphabet[pSource[3] - _offsetAlphabetMinChar] * _pow85_1 +
                                            pIndexToAlphabet[pSource[4] - _offsetAlphabetMinChar] * _pow85_2 +
                                            pIndexToAlphabet[pSource[5] - _offsetAlphabetMinChar] * _pow85_3 +
                                            pIndexToAlphabet[pSource[6] - _offsetAlphabetMinChar] * _pow85_4 +
                                            pIndexToAlphabet[pSource[7] - _offsetAlphabetMinChar] * _pow85_5);
                        #endregion

                        using (var ms = _memManager.GetStream(String.Empty, totalLength, false))
                        {
                            uint sum;

                            #region Only complete 4 byte blocks (we have no padding)
                            //omit prefix, original length of byte array saved to encoded string and suffix
                            for (int i = 8; i < s.Length - 2;)
                            {
                                switch (pSource[i])
                                {
                                    case _compressCharSameSum:
                                        ms.Write(decodedBlock, 0, 4); //decoded block already filled with correct values
                                        i++;
                                        break;

                                    case _compressCharAll32: //all bytes have value of 32
                                        for (int j = 0; j < 4; j++)
                                        {
                                            pBlock[j] = 32;
                                        }

                                        ms.Write(decodedBlock, 0, 4);
                                        i++;
                                        break;

                                    case _compressCharAll48: //all bytes have value of 48
                                        for (int j = 0; j < 4; j++)
                                        {
                                            pBlock[j] = 48;
                                        }

                                        ms.Write(decodedBlock, 0, 4);
                                        i++;
                                        break;

                                    default:
                                        sum = pIndexToAlphabet[pSource[i] - _offsetAlphabetMinChar] * _pow85_1 +
                                            pIndexToAlphabet[pSource[i + 1] - _offsetAlphabetMinChar] * _pow85_2 +
                                            pIndexToAlphabet[pSource[i + 2] - _offsetAlphabetMinChar] * _pow85_3 +
                                            pIndexToAlphabet[pSource[i + 3] - _offsetAlphabetMinChar] * _pow85_4 +
                                            pIndexToAlphabet[pSource[i + 4] - _offsetAlphabetMinChar] * _pow85_5;

                                        pBlock[3] = (byte)(sum & 0xff);
                                        pBlock[2] = (byte)((sum & 0xff00) >> 8);
                                        pBlock[1] = (byte)((sum & 0xff0000) >> 16);
                                        pBlock[0] = (byte)((sum & 0xff000000) >> 24);

                                        ms.Write(decodedBlock, 0, 4);

                                        i += 5;
                                        break;
                                }
                            }
                            #endregion

                            return ms.ToArray();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Decodes a compressed base85 string with padding internally.
        /// </summary>
        /// <param name="s">String to decode.</param>
        /// <returns>Decoded byte array.</returns>
        private unsafe static byte[] DecodeCompressPadding(string s)
        {
            byte[] decodedBlock = new byte[4];

            fixed (char* pSource = s)
            {
                fixed (uint* pIndexToAlphabet = _indexToAlphabet)
                {
                    fixed (byte* pBlock = decodedBlock)
                    {
                        #region Length of original byte array before encoding
                        int totalLength = (int)(
                                            pIndexToAlphabet[pSource[3] - _offsetAlphabetMinChar] * _pow85_1 +
                                            pIndexToAlphabet[pSource[4] - _offsetAlphabetMinChar] * _pow85_2 +
                                            pIndexToAlphabet[pSource[5] - _offsetAlphabetMinChar] * _pow85_3 +
                                            pIndexToAlphabet[pSource[6] - _offsetAlphabetMinChar] * _pow85_4 +
                                            pIndexToAlphabet[pSource[7] - _offsetAlphabetMinChar] * _pow85_5);
                        #endregion

                        using (var ms = _memManager.GetStream(String.Empty, totalLength, false))
                        {
                            uint sum;

                            #region Complete 4 byte blocks
                            //omit prefix, original length of byte array saved to encoded string and suffix
                            //and handle last block seperately because of padding (padding block will always have 5 chars)
                            for (int i = 8; i < s.Length - 7;)
                            {
                                switch (pSource[i])
                                {
                                    case _compressCharSameSum:
                                        ms.Write(decodedBlock, 0, 4); //decoded block already filled with correct values
                                        i++;
                                        break;

                                    case _compressCharAll32: //all bytes have value of 32
                                        for (int j = 0; j < 4; j++)
                                        {
                                            pBlock[j] = 32;
                                        }

                                        ms.Write(decodedBlock, 0, 4);
                                        i++;
                                        break;

                                    case _compressCharAll48: //all bytes have value of 48
                                        for (int j = 0; j < 4; j++)
                                        {
                                            pBlock[j] = 48;
                                        }

                                        ms.Write(decodedBlock, 0, 4);
                                        i++;
                                        break;

                                    default:
                                        sum = pIndexToAlphabet[pSource[i] - _offsetAlphabetMinChar] * _pow85_1 +
                                            pIndexToAlphabet[pSource[i + 1] - _offsetAlphabetMinChar] * _pow85_2 +
                                            pIndexToAlphabet[pSource[i + 2] - _offsetAlphabetMinChar] * _pow85_3 +
                                            pIndexToAlphabet[pSource[i + 3] - _offsetAlphabetMinChar] * _pow85_4 +
                                            pIndexToAlphabet[pSource[i + 4] - _offsetAlphabetMinChar] * _pow85_5;

                                        pBlock[3] = (byte)(sum & 0xff);
                                        pBlock[2] = (byte)((sum & 0xff00) >> 8);
                                        pBlock[1] = (byte)((sum & 0xff0000) >> 16);
                                        pBlock[0] = (byte)((sum & 0xff000000) >> 24);

                                        ms.Write(decodedBlock, 0, 4);

                                        i += 5;
                                        break;
                                }
                            }
                            #endregion

                            #region Last block with padding
                            int offset = s.Length - 7;
                            sum = pIndexToAlphabet[pSource[offset] - _offsetAlphabetMinChar] * _pow85_1 +
                                pIndexToAlphabet[pSource[offset + 1] - _offsetAlphabetMinChar] * _pow85_2 +
                                pIndexToAlphabet[pSource[offset + 2] - _offsetAlphabetMinChar] * _pow85_3 +
                                pIndexToAlphabet[pSource[offset + 3] - _offsetAlphabetMinChar] * _pow85_4 +
                                pIndexToAlphabet[pSource[offset + 4] - _offsetAlphabetMinChar] * _pow85_5;

                            pBlock[3] = (byte)(sum & 0xff);
                            pBlock[2] = (byte)((sum & 0xff00) >> 8);
                            pBlock[1] = (byte)((sum & 0xff0000) >> 16);
                            pBlock[0] = (byte)((sum & 0xff000000) >> 24);

                            int numBytesToWrite = 4;
                            for (int i = 1; i < 4; i++)
                            {
                                if (pBlock[i] == _paddingCharIndexNoOffset)
                                {
                                    numBytesToWrite = i;
                                    break;
                                }
                            }

                            ms.Write(decodedBlock, 0, numBytesToWrite);
                            #endregion

                            return ms.ToArray();
                        }
                    }
                }
            }
        }
        #endregion

        #endregion
    }
}
